# coding=utf-8
"""
api基类
所有的api类需要从该类进行继承
author:董新强 日期:2016-01-19
"""
import json
import traceback

from tornado.concurrent import Future
from tornado.gen import coroutine
from tornado.httpclient import AsyncHTTPClient
from tornado.web import RequestHandler

from enums.enum_error_type import EnumErrorType
from framework.auth_helper import AuthHelper
from framework.log_util import log_async
from framework.msg import failure, ok
from framework.utilities import to_int, to_list, to_dict, to_string, echo
from pools import run_async
from services.admin import is_admin
from services.applications.application_manage import ApplicationManage
from setting import is_debug, AUTH_VALIDATE_URI

white_urls = {r'/muxian/cat/login/mini', '/muxian/cat/public/ability', r'/catapi/login'}


class BaseHandler(RequestHandler):
    """
    api基类
    所有的api类需要从该类进行继承
    """

    def __init__(self, application, request, **kwargs):
        super().__init__(application, request, **kwargs)

        '''  存放对post数据解析为的字典数据,调用get_post_body()时就不必重复解析了    '''
        self._post_body = None
        self._expire = 0
        self.url = None
        self._usr = None
        self.validated = False
        self.appid = None  # 应用服务的ID(对外提供服务)
        self._action = ''  # 请求类型

    @property
    def uid(self):
        return self._usr.get('id') if self._usr else 0

    @property
    def usr(self):
        return self._usr

    @staticmethod
    @coroutine
    def to_reponse(self, res):
        try:
            if isinstance(res, Future):
                res = yield res

            if isinstance(res, dict):
                if EnumErrorType.param_error.value in res.get('msg', ''):
                    BaseHandler._set_http_code(self, failure(error_type=EnumErrorType.param_error))
                return self.finish(res)

            if isinstance(res, ok):
                return self.finish(res.responseMsg())

            if isinstance(res, failure):
                if res.error_type == EnumErrorType.param_error.value:
                    BaseHandler._set_http_code(self, failure(error_type=EnumErrorType.param_error))
                return self.finish(res.responseMsg())
            self.finish(res)
        except:
            self.finish()

    def req_dict(self, key=None, tp=None):
        '''
        获取post body中的data
        '''

        def get_value_by_key(key):
            # 如果没有key的话,直接返回 post body
            if not key:
                return self._post_body

            if not tp:  # 没有指定类型就直接返回
                return self._post_body.get(key)
            if tp is int:  # int类型
                return to_int(self._post_body.get(key), 0)
            if tp is list:  # 列表类型
                return to_list(self._post_body.get(key))
            if tp is dict:  # 字典类型
                return to_dict(self._post_body.get(key))
            # 不在指定类型之列的也就自己返回
            return self._post_body.get(key)

        try:
            if self._post_body is not None:
                # 如果没有key的话,直接返回 post body
                if not key:
                    return self._post_body

                return get_value_by_key(key)

            try:
                self._post_body = json.loads(to_string(self.request.body), encoding='utf-8')
            except:
                dic = {}
                for v in self.request.arguments:
                    value = self.request.arguments[v]
                    dic[v] = to_string(value[0]) if value else None
                self._post_body = dic
            return get_value_by_key(key)

        except Exception as e:
            self._post_body = {}
            echo(e, 'req_dict')
            return None

    def get_client_ip(self):
        try:
            remote_ip = self.request.headers.get('X-Real-Ip')
            if remote_ip:
                return remote_ip
            return self.request.connection.stream.socket.getpeername()[0]
        except Exception as e:
            print(e)
        return ''

    def on_finish(self):
        try:
            self.finish()
        except:
            pass

    @coroutine
    def prepare(self):
        if self.request.method == 'OPTIONS':
            return

        self.url = self.request.path.lower().strip()
        echo(self.url)

        if self.url in white_urls:
            self.validated = True  # 允许访问
            return

        try:
            token = self.request.headers.get('Authorization')
            if not token:
                self.set_status(401, '请登陆')
                return self.finish(failure(msg='请登录').responseMsg())

            # 对外接口
            if self.url.startswith(r'/catapi/'):
                req = ApplicationManage.decrypt(token, self.get_client_ip())
                if not req.is_ok():
                    return self.finish(req.responseMsg())
                self.appid, _ = req.data
                self.validated = True
                return

            # 权限校验
            is_ok, self._usr = yield AuthHelper().validate(token)

            if not is_ok:
                self.set_status(401, '请登录')
                self.finish(failure(msg='请登录').responseMsg())
                return

            # 管理接口
            if self.url.startswith(r'/muxian/cat/admin/') and not is_admin(self.uid):
                self.set_status(401, '没有权限')
                self.finish(failure(msg='没有权限').responseMsg())
                return

            self.validated = True  # 允许访问

        except Exception as e:
            print('prepare', self.url, e)
            self.finish(failure(msg='服务器内部错误').responseMsg())

    def write_error(self, status_code, **kwargs):
        """Override """
        # ----------------- 调试模式打印相关信息 ----------------------------------
        if is_debug:
            if self.settings.get("serve_traceback") and "exc_info" in kwargs:
                print('--------------------服务器内部错误--相关信息------------')
                for line in traceback.format_exception(*kwargs["exc_info"]):
                    print(line)
                print('-------------------------------------------------------')
        # --------------------------------------------------------------------------
        self.set_status(200)
        self.finish(failure(msg='操作失败(错误代码:0x001)').responseMsg())

    @staticmethod
    def _set_http_code(self, result):
        if not isinstance(result, failure):
            return

        if result.error_type == EnumErrorType.param_error.value:
            return self.set_status(400, '参数错误')

        if result.error_type == EnumErrorType.token_error.value or result.error_type == EnumErrorType.token_expired.value:
            return self.set_status(401, '请登陆')

        if result.error_type == EnumErrorType.access_denied.value:
            return self.set_status(403, '禁止访问')

        if result.error_type == EnumErrorType.not_exists.value:
            return self.set_status(404, '未找到')

        if result.error_type == EnumErrorType.frequently.value:
            return self.set_status(429, '被限流')

        if result.error_type == EnumErrorType.interal_error.value:
            return self.set_status(500, '服务器内部错误')

        if result.error_type == EnumErrorType.not_implement.value:
            return self.set_status(501, '服务不可用')

    def options(self, *args, **kwargs):
        self.set_header("Access-Control-Allow-Headers", "xXMLHttpRequest, X_FILENAME, accept-encoding, authorization, content-type, dnt, origin, user-agent, x-csrftoken, x-requested-with, Pragma")
        self.set_header('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE, OPTIONS, VIEW')
        self.finish()

    def set_default_headers(self):
        self.set_header("Access-Control-Allow-Origin", "*")  # 这个地方可以写域名
        self.set_header("Allow", "GET, POST, HEAD, OPTIONS")
        self.set_header('Vary', 'Accept, Origin')

    @staticmethod
    def not_implement():
        return failure(msg='未实现', error_type=EnumErrorType.not_exists)

    def http_get(self):
        return BaseHandler.not_implement()

    def http_post(self):
        return BaseHandler.not_implement()

    @coroutine
    def get(self, *args, **kwargs):
        try:
            if not self.validated:
                return self.finish(failure(msg='没有权限').responseMsg())

            result = yield run_async(self.http_get)
            BaseHandler.to_reponse(self, result)
        except Exception as e:
            echo(e, 'base_api.get', self.url)
        finally:
            if not self._finished:
                self.finish()

    @coroutine
    def post(self, *args, **kwargs):
        try:
            if not self.validated:
                return self.finish(failure(msg='没有权限').responseMsg())

            result = yield run_async(self.http_post)
            BaseHandler.to_reponse(self, result)
        except Exception as e:
            echo(e, 'base_api.post', self.url)
        finally:
            if not self._finished:
                self.finish()

    @property
    def action(self):
        '''
        需要执行的操作
        '''
        if not self._action:
            self._action = self.url.split('/')[-1]
        return self._action.strip()

    @coroutine
    def do_async_action(self, allow_methods: tuple):
        '''
        执行操作
        '''
        try:
            if not self.action or self.action not in allow_methods:
                BaseHandler.to_reponse(self, self.not_implement())
                return

            fn = getattr(self, self.action)
            if not fn:
                BaseHandler.to_reponse(self, self.not_implement())
                return

            result = yield fn()
            BaseHandler.to_reponse(self, result)
        except Exception as e:
            yield log_async(e, self.url, self.req_dict())
            if not self._finished:
                self.finish()


if __name__ == '__main__':
    app_key = r'ee94a3106a5411eaa18114dda9040cae'
    token = 'Token UZKlne/I0q8AveN8GizfTqXlsqqJXPv5nw1I7O2qleasw7suBhFqCTeYtxge7L2TKSeZt1q8TlcUnd+rPDUz6mC6geokNbghTfIhbeFtk/g0EjKuC4HhCyrgh9IJhQO1'


    @coroutine
    def validate():
        http_client = AsyncHTTPClient()
        response = yield http_client.fetch(AUTH_VALIDATE_URI, method='POST', body=json.dumps({'id': app_key, 'token': token}))
        response = to_dict(response.body)
        if not response or not response['code']:
            print('失败')


    @coroutine
    def run():
        total_nums = 20
        import time
        st = time.time()
        ls = []
        for _ in range(total_nums):
            ls.append(validate())
        yield ls

        total = time.time() - st
        print(total / total_nums)
        print(total_nums / total)


    from tornado.ioloop import IOLoop

    IOLoop.current().run_sync(run)
